home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 38 / Amiga Format CD38 (1999-03-15)(Future Publishing)(GB)(Track 1 of 3)[!][issue 1999-04].iso / -seriously_amiga- / programming / other / cyberxxxsrc / cyberqt / txt / cyberqtindex.mod < prev    next >
Text File  |  1999-02-08  |  25KB  |  749 lines

  1. MODULE  CyberQTIndex;
  2.  
  3. (* $IFNOT DEBUG *)
  4.   (* $StackChk- $OvflChk- $RangeChk- $CaseChk- $ReturnChk- $NilChk- $TypeChk- $OddChk- $ClearVars- *)
  5. (* $END *)
  6.  
  7. (* /// ------------------------------- "IMPORT" -------------------------------- *)
  8. IMPORT  a:=CyberQTAudio,
  9.         BT:=BasicTypes,
  10.         cu:=CyberQTUtils,
  11.         d:=Dos,
  12.         e:=Exec,
  13.         es:=ExecSupport,
  14.         g:=CyberQTGlobals,
  15.         io:=AsyncIOSupport2,
  16.         ll:=LinkedLists,
  17.         mu:=MathUtils,
  18.         o:=CyberQTOpts,
  19.         ol:=OberonLib,
  20.         s:=CyberQTSync,
  21.         u:=Utility,
  22.         v:=CyberQTVideo,
  23.         y:=SYSTEM;
  24. (* \\\ ------------------------------------------------------------------------- *)
  25.  
  26. (* /// -------------------------------- "TYPE" --------------------------------- *)
  27. TYPE    IndexList=POINTER TO IndexListDesc;
  28.         IndexListDesc=RECORD (ll.ListDesc);
  29.         END;
  30.  
  31.         IndexNode=POINTER TO IndexNodeDesc;
  32.         IndexNodeDesc=RECORD (ll.NodeDesc)
  33.             num: LONGINT;
  34.             offset: LONGINT;
  35.             size: LONGINT;
  36.             codecID: LONGINT;
  37.             atTime: REAL;
  38.             isDummy: BOOLEAN;
  39.         END;
  40.  
  41.         AudioNode=POINTER TO AudioNodeDesc;
  42.         AudioNodeDesc=RECORD (IndexNodeDesc)
  43.             start: BOOLEAN;
  44.             splitted: BOOLEAN;
  45.             offset2: LONGINT;
  46.             size2: LONGINT;
  47.         END;
  48.  
  49.         VideoNode=POINTER TO VideoNodeDesc;
  50.         VideoNodeDesc=RECORD (IndexNodeDesc)
  51.             duration: REAL;
  52.             isKey: BOOLEAN;
  53.         END;
  54. (* \\\ ------------------------------------------------------------------------- *)
  55.  
  56. (* /// -------------------------------- "CONST" -------------------------------- *)
  57. CONST       epsilon=0.000005;
  58. (* \\\ ------------------------------------------------------------------------- *)
  59.  
  60. (* /// --------------------------------- "VAR" --------------------------------- *)
  61. VAR     videoIndexEntries: LONGINT;
  62.         audioIndexEntries: LONGINT;
  63.         onlyKeyframes: BOOLEAN;
  64.         doVideo: BOOLEAN;
  65.         doAudio: BOOLEAN;
  66.         videoList: IndexList;
  67.         audioList: IndexList;
  68.         mainIndex: IndexList;
  69.         keyframeCount: LONGINT;
  70.         aInitDuration: REAL;
  71.         aStartOffset: REAL;
  72.         vInitDuration: REAL;
  73.         vStartOffset: REAL;
  74.         pause: BOOLEAN;
  75. (* \\\ ------------------------------------------------------------------------- *)
  76.  
  77. (* /// ------------------------ "PROCEDURE TestIndex()" ------------------------ *)
  78. PROCEDURE TestIndex(node: BT.ANY;
  79.                     x: BT.ANY);
  80.  
  81. VAR     time: e.STRING;
  82.         dur: e.STRING;
  83.  
  84. BEGIN
  85.   IF node IS VideoNode THEN
  86.     WITH node: VideoNode DO
  87.       mu.real2str(node.atTime,time,4);
  88.       mu.real2str(node.duration,dur,4);
  89.       d.PrintF("video node %5ld, offset: %8ld, size: %8ld, codec: %1ld, time: %10s, duration: %7s, key: %2ld\n",node.num,node.offset,node.size,node.codecID,y.ADR(time),y.ADR(dur),y.VAL(SHORTINT,node.isKey));
  90.     END;
  91.   ELSIF node IS AudioNode THEN
  92.     WITH node: AudioNode DO
  93.       mu.real2str(node.atTime,time,4);
  94.       d.PrintF("audio node %5ld, offset: %8ld, size: %8ld, codec: %1ld, time: %10s, start: %2ld, splitted: %2ld, offset2: %8ld, size2: %8ld\n",node.num,node.offset,node.size,node.codecID,y.ADR(time),y.VAL(SHORTINT,node.start),y.VAL(SHORTINT,node.splitted),node.offset2,node.size2);
  95.     END;
  96.   END;
  97. END TestIndex;
  98. (* \\\ ------------------------------------------------------------------------- *)
  99.  
  100. (* /// ---------------------- "PROCEDURE AudioPreload()" ----------------------- *)
  101. PROCEDURE AudioPreload(node: BT.ANY;
  102.                        x: BT.ANY);
  103. BEGIN
  104.   IF node IS AudioNode THEN
  105.     WITH node: AudioNode DO
  106.       a.DecodeFrame(node.offset,node.size,node.offset2,node.size2,node.codecID);
  107.     END;
  108.   END;
  109. END AudioPreload;
  110. (* \\\ ------------------------------------------------------------------------- *)
  111.  
  112. (* /// --------------------- "PROCEDURE CountKeyframes()" ---------------------- *)
  113. PROCEDURE CountKeyframes(node: BT.ANY;
  114.                          x: BT.ANY);
  115. BEGIN
  116.   IF node IS VideoNode THEN
  117.     WITH node: VideoNode DO
  118.       IF node.isKey THEN INC(keyframeCount); END;
  119.     END;
  120.   END;
  121. END CountKeyframes;
  122. (* \\\ ------------------------------------------------------------------------- *)
  123.  
  124. (* /// ------------------------- "PROCEDURE Enqueue()" ------------------------- *)
  125. PROCEDURE (list: IndexList) Enqueue(n: IndexNode);
  126.  
  127. VAR     x: IndexNode;
  128.  
  129. BEGIN
  130.   IF list.isEmpty() THEN
  131.     list.AddHead(n);
  132.   ELSE
  133.     x:=list.head(IndexNode);
  134.     WHILE (x.atTime+epsilon<=n.atTime) & (x.next#NIL) DO x:=x.next(IndexNode); END;
  135.     IF x.offset>n.offset THEN
  136.       IF (x.atTime=n.atTime) OR ((x.prev#NIL) & (x.prev(IndexNode).offset>n.offset)) THEN
  137.         WHILE (x.prev#NIL) & (x.prev(IndexNode).offset>n.offset) DO x:=x.prev(IndexNode); END;
  138.       END;
  139.     ELSE
  140. (*
  141.       IF (x.atTime=n.atTime) OR ((x.next#NIL) & (x.offset<n.offset)) THEN
  142.         WHILE (x.next#NIL) & (x.offset<n.offset) DO x:=x.next(IndexNode); END;
  143.       END;
  144. *)
  145.     END;
  146.     IF (x IS AudioNode) THEN
  147.       WITH x: AudioNode DO
  148.         IF x.splitted & (x.offset2=-1) THEN (* 2.Teil noch nicht belegt *)
  149.           x.offset2:=n.offset;
  150.           x.size2:=n.size;
  151.         ELSE (* sonst normal einfügen, ist 1.Teil *)
  152.           IF x.atTime<n.atTime THEN
  153.             x.AddBehind(n);
  154.           ELSE
  155.             x.AddBefore(n);
  156.           END;
  157.         END;
  158.       END;
  159.     ELSE
  160.       WITH x: VideoNode DO
  161.         IF x.offset<n.offset THEN
  162.           x.AddBehind(n);
  163.         ELSE
  164.           x.AddBefore(n);
  165.         END;
  166.       END;
  167.     END;
  168.   END;
  169. END Enqueue;
  170. (* \\\ ------------------------------------------------------------------------- *)
  171.  
  172. (* /// ---------------------- "PROCEDURE HandleEvents()" ----------------------- *)
  173. PROCEDURE HandleEvents(): LONGINT;
  174.  
  175. VAR     sigs: LONGSET;
  176.         received: LONGSET;
  177.         ret: LONGINT;
  178.  
  179. BEGIN
  180.   ret:=g.noError;
  181.   sigs:=LONGSET{s.timerSig,v.idcmpSig,d.ctrlC,d.ctrlD};
  182.   LOOP
  183.     received:=e.Wait(sigs+a.audioSigs);
  184.     IF doAudio THEN a.PlaySample(FALSE); END;
  185.     IF v.idcmpSig IN received THEN ret:=v.HandleIDCMP(); END;
  186.     IF s.timerSig IN received THEN EXIT END;
  187.     IF d.ctrlC IN received THEN ret:=d.break; END;
  188.     IF d.ctrlD IN received THEN ret:=g.skipAnim; END;
  189. (*
  190.     IF ret=g.pauseAnim THEN
  191.       ret:=g.noError;
  192.       pause:=~pause;
  193.       IF pause THEN
  194.         a.PauseSound(pause);
  195.         s.PauseTimer(pause);
  196.         EXCL(sigs,s.timerSig);
  197.       ELSE
  198.         INCL(sigs,s.timerSig);
  199.         s.PauseTimer(pause);
  200.         a.PauseSound(pause);
  201.       END;
  202.     END;
  203. *)
  204.     IF ret#g.noError THEN EXIT; END;
  205.   END;
  206.   RETURN ret;
  207. END HandleEvents;
  208. (* \\\ ------------------------------------------------------------------------- *)
  209.  
  210. (* /// --------------------- "PROCEDURE BuildVideoIndex()" --------------------- *)
  211. PROCEDURE BuildVideoIndex();
  212.  
  213. VAR     track: g.TrackPtr;
  214.         node: VideoNode;
  215.         node2: VideoNode;
  216.         chunk: LONGINT;
  217.         sample: LONGINT;
  218.         size: LONGINT;
  219.         sample2Chunk: LONGINT;
  220.         nextSample2Chunk: LONGINT;
  221.         time2Sample: LONGINT;
  222.         nextTime2Sample: LONGINT;
  223.         syncSample: LONGINT;
  224.         offset: LONGINT;
  225.         numSamples: LONGINT;
  226.         timeOffset: REAL;
  227.         lastDuration: REAL;
  228.         cur: LONGINT;
  229.         timePerFrame: REAL;
  230.         curEdit: LONGINT;
  231.         curEditDuration: REAL;
  232.         movieTime: REAL;
  233.         trackTime: REAL;
  234.         mediaTime: REAL;
  235.         movieScale: LONGINT;
  236.         trackScale: LONGINT;
  237.         timeFactor: REAL;
  238.  
  239. BEGIN
  240.   doVideo:=v.AllocBuffers() & ~o.noVideo;
  241. (* /// "$IF RUNDEBUG" *)
  242.   IF o.debug THEN d.PrintF("build video index\n"); END;
  243. (* \\\ $END *)
  244.   videoList.Init();
  245.   IF ~es.ListEmpty(g.animInfo.videoTracks) THEN
  246.     movieTime:=g.animInfo.mvhd.duration/g.animInfo.mvhd.timeScale;
  247.     movieScale:=g.animInfo.mvhd.timeScale;
  248.     cur:=0;
  249.     track:=g.animInfo.videoTracks.head;
  250.     WHILE track.node.succ#NIL DO
  251.       trackTime:=track.head.duration/g.animInfo.mvhd.timeScale;
  252.       trackScale:=track.mediaHead.timeScale;
  253.       mediaTime:=track.mediaHead.duration/track.mediaHead.timeScale;
  254.       timeFactor:=1.0;
  255.       timeOffset:=vInitDuration;
  256.       IF track.edits#NIL THEN
  257.         IF track.edits[0].mediaTime#-1 THEN curEditDuration:=track.edits[0].duration/movieScale; END;
  258.         IF trackTime<mediaTime THEN timeFactor:=trackTime/mediaTime; END; (* künstlich verkürzen? meist ca 99% statt 100% *)
  259.       END;
  260.       onlyKeyframes:=(track.syncs#NIL) & (track.syncEntries=track.sizeEntries) OR (track.syncEntries=0);
  261.       sample:=0;
  262.       sample2Chunk:=0;
  263.       nextSample2Chunk:=track.samples[1].firstChunk;
  264.       time2Sample:=0;
  265.       IF track.times#NIL THEN
  266.         nextTime2Sample:=track.times[0].count;
  267.       ELSE
  268.         nextTime2Sample:=-1;
  269.         timePerFrame:=track.mediaHead.duration/trackScale/track.sizeEntries;
  270.       END;
  271.       syncSample:=0;
  272.       curEdit:=0;
  273.       FOR chunk:=0 TO track.offsetEntries-1 DO
  274.         IF (chunk=nextSample2Chunk) & (sample2Chunk+1<track.sampleEntries) THEN
  275.           INC(sample2Chunk);
  276.           nextSample2Chunk:=track.samples[sample2Chunk+1].firstChunk;
  277.         END;
  278.         numSamples:=track.samples[sample2Chunk].samplesPerChunk;
  279.         offset:=track.offsets[chunk];
  280.         WHILE numSamples>0 DO
  281.           DEC(numSamples);
  282.           size:=track.sizes[sample];
  283.           NEW(node);
  284.           node.num:=cur;
  285.           node.size:=size;
  286.           node.offset:=offset;
  287.           node.codecID:=track.samples[sample2Chunk].descriptionID;
  288.           node.atTime:=timeOffset;
  289.           IF track.times#NIL THEN
  290.             lastDuration:=track.times[time2Sample].duration/trackScale;
  291.           ELSE
  292.             lastDuration:=timePerFrame;
  293.           END;
  294.           node.duration:=lastDuration*timeFactor;
  295.           IF track.syncs#NIL THEN
  296.             IF syncSample<track.syncEntries THEN
  297.               IF track.syncs[syncSample]=sample THEN
  298.                 INC(syncSample);
  299.                 node.isKey:=TRUE;
  300.               ELSE
  301.                 node.isKey:=FALSE;
  302.               END;
  303.             ELSE
  304.               node.isKey:=FALSE;
  305.             END;
  306.           ELSE
  307.             node.isKey:=TRUE;
  308.           END;
  309.           node.isDummy:=FALSE;
  310.           videoList.AddTail(node);
  311.           INC(sample);
  312.           INC(offset,size);
  313.  
  314.           IF (sample=nextTime2Sample) & (time2Sample+1<track.timeEntries) THEN
  315.             INC(time2Sample);
  316.             INC(nextTime2Sample,track.times[time2Sample].count);
  317.           END;
  318.  
  319.           IF (track.edits#NIL) & (track.editEntries>1) THEN
  320.             curEditDuration:=curEditDuration-lastDuration;
  321.             IF curEditDuration<=epsilon THEN (* edit-Zeit abgelaufen? dann nächsten Edit*)
  322.               INC(curEdit);
  323.               IF curEdit<track.editEntries THEN
  324.                 curEditDuration:=track.edits[curEdit].duration/movieScale;
  325.                 timeOffset:=track.edits[curEdit].mediaTime/trackScale;
  326.                 lastDuration:=0.0;
  327.                 IF track.edits[curEdit-1].mediaTime/trackScale+track.edits[curEdit-1].duration/movieScale>=timeOffset THEN
  328.                   node:=videoList.tail(VideoNode);
  329.                   WHILE node.atTime>timeOffset DO node:=node.prev(VideoNode); END;
  330.                   WHILE node.atTime<timeOffset DO
  331.                     NEW(node2);
  332.                     INC(cur);
  333.                     node2.num:=cur;
  334.                     node2.offset:=node.offset;
  335.                     node2.size:=node.size;
  336.                     node2.codecID:=node.codecID;
  337.                     node2.atTime:=node.atTime+node.duration;
  338.                     node2.duration:=node.duration;
  339.                     node2.isKey:=node.isKey;
  340.                     videoList.AddTail(node2); (* letzten Knoten nochmal einhängen *)
  341.                     node:=node.next(VideoNode);
  342.                   END;
  343.                 END;
  344.               END;
  345.             END;
  346.           END;
  347.           timeOffset:=timeOffset+lastDuration;
  348.           INC(cur);
  349.         END;
  350.       END;
  351.       track:=track.node.succ;
  352.     END;
  353.   ELSE
  354.     doVideo:=FALSE;
  355.   END;
  356.   WHILE videoList.nbElements()<nextTime2Sample DO
  357.     node:=videoList.tail(VideoNode);
  358.     NEW(node2);
  359.     INC(cur);
  360.     node2.num:=cur;
  361.     node2.offset:=node.offset;
  362.     node2.size:=node.size;
  363.     node2.codecID:=node.codecID;
  364.     node2.atTime:=node.atTime+node.duration;
  365.     node2.duration:=node.duration;
  366.     node2.isKey:=node.isKey;
  367.     videoList.AddTail(node2); (* letzten Knoten nochmal einhängen *)
  368. (* /// "$IF RUNDEBUG" *)
  369.     IF o.debug THEN d.PrintF("added extra video frame\n"); END;
  370. (* \\\ $END *)
  371.   END;
  372.   videoIndexEntries:=videoList.nbElements();
  373.  
  374.   IF vInitDuration>0.0 THEN
  375.     NEW(node);
  376.     node.num:=-1;
  377.     node.size:=0;
  378.     node.offset:=-1;
  379.     node.codecID:=-1;
  380.     node.atTime:=MIN(REAL);
  381.     node.duration:=vInitDuration;
  382.     node.isDummy:=TRUE;
  383.     node.isKey:=FALSE;
  384.     videoList.AddHead(node);
  385.   END;
  386.  
  387.   NEW(node);
  388.   node.num:=-2;
  389.   node.offset:=-1;
  390.   node.size:=0;
  391.   node.codecID:=0;
  392.   node.atTime:=MAX(REAL);
  393.   node.duration:=0.0;
  394.   node.isKey:=TRUE;
  395.   videoList.AddTail(node); (* ein Dummynode *)
  396. END BuildVideoIndex;
  397. (* \\\ ------------------------------------------------------------------------- *)
  398.  
  399. (* /// -------------------- "PROCEDURE MergeAudioFrames()" --------------------- *)
  400. PROCEDURE MergeAudioFrames();
  401.  
  402. VAR     node: AudioNode;
  403.         node2: AudioNode;
  404.         numMerged: LONGINT;
  405.  
  406. BEGIN
  407.   node:=audioList.head(AudioNode);
  408.   numMerged:=0;
  409.   WHILE node.next#NIL DO
  410.     node2:=node.next(AudioNode);
  411.     IF (node.offset+node.size=node2.offset) & (node.codecID=node2.codecID) & (node.size+node2.size<=a.audioBufferSize) THEN
  412.       node2.offset:=node.offset;
  413.       node2.size:=node2.size+node.size;
  414.       node2.atTime:=node.atTime;
  415.       node2.start:=node2.start OR node.start;
  416.       node.Remove();
  417.       INC(numMerged);
  418.     END;
  419.     node:=node2;
  420.   END;
  421. (* /// "$IF RUNDEBUG" *)
  422.   IF o.debug & (numMerged>0) THEN d.PrintF("merged %ld audio frames\n",numMerged); END;
  423. (* \\\ $END *)
  424. END MergeAudioFrames;
  425. (* \\\ ------------------------------------------------------------------------- *)
  426.  
  427. (* /// --------------------- "PROCEDURE BuildAudioIndex()" --------------------- *)
  428. PROCEDURE BuildAudioIndex();
  429.  
  430. VAR     track: g.TrackPtr;
  431.         node: AudioNode;
  432.         chunk: LONGINT;
  433.         sample2Chunk: LONGINT;
  434.         nextSample2Chunk: LONGINT;
  435.         timeOffset: REAL;
  436.         size: LONGINT;
  437.         mainTimeScale: LONGINT;
  438.         curEdit: LONGINT;
  439.         inc: REAL;
  440.         cur: LONGINT;
  441.  
  442. BEGIN
  443.   doAudio:=a.AllocBuffers() & ~o.noSound & a.audioOpen;
  444. (* /// "$IF RUNDEBUG" *)
  445.   IF o.debug THEN d.PrintF("build audio index\n"); END;
  446. (* \\\ $END *)
  447.   audioList.Init();
  448.   IF ~es.ListEmpty(g.animInfo.audioTracks) THEN
  449.     track:=g.animInfo.audioTracks.head;
  450.     cur:=0;
  451.     WHILE track.node.succ#NIL DO
  452.       size:=track.samples[0].samplesPerChunk;
  453.       timeOffset:=aInitDuration;
  454. (* ///
  455.       IF track.edits#NIL THEN
  456.         IF track.edits[0].mediaTime=-1 THEN
  457.           timeOffset:=track.edits[0].duration/g.animInfo.mvhd.timeScale;
  458.         ELSE
  459.           timeOffset:=track.edits[0].mediaTime/track.mediaHead.timeScale;
  460.         END;
  461.       ELSE
  462.         timeOffset:=0.0;
  463.       END;
  464. \\\ *)
  465.       inc:=size/track.mediaHead.timeScale;
  466.       timeOffset:=timeOffset-inc; (* negative Werte, damit Audio vor Video gelesen wird *)
  467.       sample2Chunk:=0;
  468.       nextSample2Chunk:=track.samples[1].firstChunk;
  469.       FOR chunk:=0 TO track.offsetEntries-1 DO
  470.         IF (chunk=nextSample2Chunk) & (sample2Chunk+1<track.sampleEntries) THEN
  471.           INC(sample2Chunk);
  472.           nextSample2Chunk:=track.samples[sample2Chunk+1].firstChunk;
  473.         END;
  474.         size:=track.samples[sample2Chunk].samplesPerChunk;
  475.         NEW(node);
  476.         node.num:=cur;
  477.         node.size:=size;
  478.         node.offset:=track.offsets[chunk];
  479.         node.atTime:=timeOffset;
  480.         node.isDummy:=FALSE;
  481.         node.start:=(cur=1);
  482.         node.splitted:=a.splittedStereo;
  483.         node.offset2:=-1;
  484.         node.size2:=-1;
  485.         audioList.AddTail(node);
  486.         timeOffset:=timeOffset+size/track.mediaHead.timeScale;
  487.         INC(cur);
  488.       END;
  489.       track:=track.node.succ;
  490.     END;
  491.   ELSE
  492.     doAudio:=FALSE;
  493.   END;
  494.   audioIndexEntries:=audioList.nbElements();
  495.   IF audioIndexEntries=1 THEN audioList.head(AudioNode).start:=TRUE; END; (* bei nur einem Sample gleich beim ersten starten *)
  496.  
  497.   IF aInitDuration>0.0 THEN
  498.     track:=g.animInfo.audioTracks.head;
  499.     NEW(node);
  500.     node.num:=-1;
  501.     inc:=(g.animInfo.mvhd.duration-track.head.duration)/g.animInfo.mvhd.timeScale;
  502.     IF inc-aInitDuration<=0 THEN inc:=aInitDuration; ELSE inc:=inc-aInitDuration; END;
  503.     node.size:=mu.floor(inc*track.mediaHead.timeScale);
  504.     node.offset:=-1;
  505.     node.codecID:=-1;
  506.     node.atTime:=MIN(REAL);
  507.     node.isDummy:=TRUE;
  508.     node.start:=FALSE;
  509.     node.splitted:=a.splittedStereo;
  510.     node.offset2:=-1;
  511.     node.size2:=0;
  512.     audioList.AddHead(node);
  513.   END;
  514.  
  515.   NEW(node);
  516.   node.num:=-2;
  517.   node.offset:=-1;
  518.   node.size:=0;
  519.   node.codecID:=0;
  520.   node.atTime:=MAX(REAL);
  521.   node.start:=FALSE;
  522.   audioList.AddTail(node); (* ein Dummynode *)
  523.  
  524.   MergeAudioFrames();
  525. END BuildAudioIndex;
  526. (* \\\ ------------------------------------------------------------------------- *)
  527.  
  528. (* /// ----------------------- "PROCEDURE JoinIndices()" ----------------------- *)
  529. PROCEDURE JoinIndices();
  530.  
  531. VAR     node: IndexNode;
  532.         firstAudio: LONGINT;
  533.         lastAudio: LONGINT;
  534.         firstVideo: LONGINT;
  535.         lastVideo: LONGINT;
  536.         fileSize: LONGINT;
  537.  
  538. BEGIN
  539.   mainIndex.Init();
  540.   firstAudio:=audioList.head(IndexNode).offset;
  541.   lastAudio:=audioList.tail(IndexNode).offset;
  542.   firstVideo:=videoList.head(IndexNode).offset;
  543.   lastVideo:=videoList.tail(IndexNode).offset;
  544. (*
  545.   fileSize:=io.FileSize(g.qtFile);
  546. *)
  547.   fileSize:=MAX(LONGINT);
  548.   REPEAT
  549.     node:=videoList.RemHead()(IndexNode);
  550.     IF node.num#-2 THEN
  551.       firstVideo:=mu.min(firstVideo,node.offset);
  552.       lastVideo:=mu.max(lastVideo,node.offset);
  553.       mainIndex.AddTail(node);
  554.     END;
  555.   UNTIL videoList.isEmpty();
  556.   REPEAT
  557.     node:=audioList.RemHead()(IndexNode);
  558.     IF node.num#-2 THEN
  559.       firstAudio:=mu.min(firstAudio,node.offset);
  560.       lastAudio:=mu.max(lastAudio,node.offset);
  561.       mainIndex.Enqueue(node);
  562.     END;
  563.   UNTIL audioList.isEmpty();
  564.   IF (doVideo & (lastVideo>fileSize)) OR (doAudio & (lastAudio>fileSize)) THEN
  565.     d.PrintF("File seems to be trancated!\n"
  566.              "File size is %lD bytes, but should be at least %lD bytes!\n"
  567.              "Playback may cause crashes!!!\n",fileSize,mu.min(lastVideo,lastAudio));
  568.   END;
  569.   IF ~o.audioPreload & doVideo & doAudio & (audioIndexEntries#0) & ((lastAudio<firstVideo) OR (firstAudio>lastVideo)) THEN d.PrintF("Audio and video data are not interleaved. Try option AUDIOPRELOAD if playback should be unsync.\n"); END;
  570.   keyframeCount:=0;
  571.   IF doVideo & o.doSkip THEN
  572.     mainIndex.Do(CountKeyframes,NIL);
  573.     node:=mainIndex.tail(IndexNode);
  574.     WHILE ~(node IS VideoNode) DO node:=node.prev(IndexNode); END;
  575.     IF keyframeCount<mu.floor(node(VideoNode).atTime+node(VideoNode).duration) THEN (* weniger als ein Key/sec *)
  576.       d.PrintF("Not enough key frames (%ld), possible skips may produce wrong frames.\n",keyframeCount);
  577.     END;
  578.   END;
  579.  
  580.   NEW(node);
  581.   node.num:=-2;
  582.   node.offset:=0;
  583.   node.size:=0;
  584.   node.codecID:=0;
  585.   node.atTime:=0.0;
  586.   mainIndex.AddTail(node);
  587.   (* mainIndex.Do(TestIndex,NIL); HALT(0); *)
  588. END JoinIndices;
  589. (* \\\ ------------------------------------------------------------------------- *)
  590.  
  591. (* /// ---------------------- "PROCEDURE BuildIndices()" ----------------------- *)
  592. PROCEDURE BuildIndices * ();
  593.  
  594. VAR     track: g.TrackPtr;
  595.         movieScale: LONGINT;
  596.  
  597. BEGIN
  598.   movieScale:=g.animInfo.mvhd.timeScale;
  599.   vInitDuration:=0;
  600.   vStartOffset:=0;
  601.   aInitDuration:=0;
  602.   aStartOffset:=0;
  603.   IF ~es.ListEmpty(g.animInfo.videoTracks) THEN
  604.     track:=g.animInfo.videoTracks.head;
  605.     IF track.edits#NIL THEN
  606.       IF track.edits[0].mediaTime=-1 THEN
  607.         vInitDuration:=track.edits[0].duration/movieScale;
  608.       ELSE
  609.         vStartOffset:=track.edits[0].mediaTime/track.mediaHead.timeScale;
  610.       END;
  611.     END;
  612.   END;
  613.   IF ~es.ListEmpty(g.animInfo.audioTracks) THEN
  614.     track:=g.animInfo.audioTracks.head;
  615.     IF track.edits#NIL THEN
  616.       IF track.edits[0].mediaTime=-1 THEN
  617.         aInitDuration:=track.edits[0].duration/movieScale;
  618.       ELSE
  619.         aStartOffset:=track.edits[0].mediaTime/track.mediaHead.timeScale;
  620.       END;
  621.     END;
  622.   END;
  623.   vInitDuration:=vInitDuration+aStartOffset;
  624.   aInitDuration:=aInitDuration+vStartOffset;
  625.   BuildVideoIndex();
  626.   BuildAudioIndex();
  627.   JoinIndices();
  628. END BuildIndices;
  629. (* \\\ ------------------------------------------------------------------------- *)
  630.  
  631. (* /// ------------------------ "PROCEDURE Playback()" ------------------------- *)
  632. PROCEDURE Playback * (fileName: e.STRPTR): LONGINT;
  633.  
  634. VAR     errVal: LONGINT;
  635.         vFramesDone: LONGINT;
  636.         vFramesSkipped: LONGINT;
  637.         skipping: BOOLEAN;
  638.         sigs: LONGSET;
  639.         node: IndexNode;
  640.         time: e.STRING;
  641.         audioStart: BOOLEAN;
  642.         preLoaded: BOOLEAN;
  643.  
  644. BEGIN
  645.   IF ~doVideo & ~doAudio THEN
  646.     d.PrintF("Neither video nor audio samples to play!\n");
  647.     RETURN g.noError;
  648.   END;
  649.   IF doAudio & (o.audioPreload OR (audioIndexEntries=1)) THEN
  650. (* /// "$IF RUNDEBUG" *)
  651.     IF o.debug THEN d.PrintF("preloading audio frames\n"); END;
  652. (* \\\ $END *)
  653.     mainIndex.Do(AudioPreload,NIL);
  654.     preLoaded:=TRUE;
  655.   ELSE
  656.     preLoaded:=FALSE;
  657.   END;
  658.   errVal:=v.OpenDisplay(~doVideo,fileName);
  659.   IF errVal#g.noError THEN
  660.     v.CloseDisplay();
  661.     RETURN errVal;
  662.   END;
  663.   vFramesSkipped:=0;
  664.   IF doVideo THEN
  665.     vFramesDone:=0;
  666.     s.Wait(o.startDelay);
  667.   ELSE
  668.     vFramesDone:=1;
  669.   END;
  670.   LOOP
  671.     skipping:=FALSE;
  672.     audioStart:=FALSE;
  673.     pause:=FALSE;
  674.     node:=mainIndex.head(IndexNode);
  675.     REPEAT
  676.       IF (node IS VideoNode) & doVideo THEN
  677.         WITH node: VideoNode DO
  678. (* /// "$IF RUNDEBUG" *)
  679.           IF o.debug THEN
  680.             mu.real2str(node.atTime,time,4);
  681.             d.PrintF("video sample: %5ld, size: %8ld, offset: %8ld, iskey: %2ld, codecID: %ld, time: %8s\n",node.num,node.size,node.offset,y.VAL(SHORTINT,node.isKey),node.codecID,y.ADR(time));
  682.           END;
  683. (* \\\ $END *)
  684.           IF vFramesDone=0 THEN s.StartTimer(); END;
  685.           errVal:=HandleEvents();
  686.           IF skipping & node.isKey & ~onlyKeyframes THEN skipping:=FALSE; END;
  687.           IF skipping THEN
  688.             INC(vFramesSkipped);
  689.           ELSE
  690.             IF ~node.isDummy THEN
  691.               v.DecodeFrame(node.offset,node.size,node.codecID);
  692.             ELSE
  693.               DEC(vFramesDone);
  694.             END;
  695.           END;
  696.           s.DoFrameDelay(node.duration,skipping);
  697.           skipping:=o.doSkip &
  698.                     ~s.IsSync() &
  699.                     (~node.isKey OR onlyKeyframes) OR
  700.                     (skipping & ~onlyKeyframes);
  701.           INC(vFramesDone);
  702.         END;
  703.       ELSIF (node IS AudioNode) & doAudio THEN
  704.         WITH node: AudioNode DO
  705. (* /// "$IF RUNDEBUG" *)
  706.           IF o.debug THEN
  707.             mu.real2str(node.atTime,time,4);
  708.             d.PrintF("audio sample: %5ld, size: %8ld, offset: %8ld,            codecID: %ld, time: %8s\n",node.num,node.size,node.offset,node.codecID,y.ADR(time));
  709.           END;
  710. (* \\\ $END *)
  711.           IF ~preLoaded THEN
  712.             IF node.isDummy THEN
  713.               a.DecodeDummyFrame(node.size);
  714.             ELSE
  715.               a.DecodeFrame(node.offset,node.size,node.offset2,node.size2,node.codecID);
  716.             END;
  717.           END;
  718.           a.PlaySample(~doVideo);
  719.           IF node.start & ~audioStart THEN audioStart:=TRUE; END;
  720.         END;
  721.       END;
  722.  
  723.       IF doAudio & audioStart & (vFramesDone>0) & ((node IS VideoNode) OR ~doVideo) THEN a.StartSound(); END;
  724.  
  725.       sigs:=e.SetSignal(LONGSET{},LONGSET{d.ctrlC,d.ctrlD});
  726.       IF d.ctrlC IN sigs THEN errVal:=d.break; END;
  727.       IF d.ctrlD IN sigs THEN errVal:=g.skipAnim; END;
  728.       IF ~g.qtFile.readOk THEN errVal:=g.readError; END;
  729.       IF errVal#g.noError THEN EXIT; END;
  730.       node:=node.next(IndexNode);
  731.     UNTIL (node.num=-2) OR (errVal#g.noError);
  732.     a.Wait4LastSample(errVal#g.noError);
  733.     IF ~o.doLoop THEN EXIT; END;
  734.   END;
  735.   s.Wait4LastFrame();
  736.   IF doVideo & o.doStats THEN s.DoStats(vFramesDone,vFramesSkipped,videoIndexEntries); END;
  737.   a.StopSound(errVal#g.noError);
  738.   v.CloseDisplay();
  739.   RETURN errVal;
  740. END Playback;
  741. (* \\\ ------------------------------------------------------------------------- *)
  742.  
  743. BEGIN
  744.   NEW(videoList);
  745.   NEW(audioList);
  746.   NEW(mainIndex);
  747. CLOSE
  748. END CyberQTIndex.
  749.